import {
  Call,
  Expression,
  Identifier,
  Statement,
  Update,

} from './types'

const buildin = [
  'any',
  'bool',
  'byte',
  'copy',
  'error',
  'f32',
  'f64',
  'float',
  'int',
  'regex',
  'rune',
  's16',
  's32',
  's64',
  's8',
  'sizeof',
  'string',
  'time',
  'typeof',
  'u16',
  'u32',
  'u64',
  'u8',
  'uint',
].sort((a, b) => {
  return a.length > b.length && 1
  || a.length < b.length && -1
  || a > b && 1 || -1
})

const
  MODULE    = {name: 'module'},
  VALUE     = {name: 'value'},
  CLASS     = {name: 'class'},
  FORK      = {name: 'fork'},
  TYPE      = {name: 'type'},
  TRAIT     = {name: 'trait'},
  ENUM      = {name: 'enum'},
  FUNC      = {name: 'function'},
  METHOD    = {name: 'method'},
  GENERIC   = {name: 'generic'},
  SPECIFIER = {name: 'specifier'}

class Local {
  constructor (senior, kind) {
    this.kind   = kind || VALUE
    this.value  = []
    this.senior = senior
  }

  get (name) {
    return this.value.includes(name)
      && this.kind
      || this.senior && this.senior.get(name)
      || null
  }

  push (name) {
    this.value.push(name)
  }
}

class Global {
  constructor () {
    this.local    = null
    this.func     = []
    this.type     = []
    this.value    = []
    this.trait    = []
    this.enum     = []
    this.method   = []
    this.class    = []
    this.generic  = []
  }

  get (name) {
    return this.local.get(name)
      || this.type.includes(name) && TYPE
      || this.class.includes(name) && CLASS
      || this.trait.includes(name) && TRAIT
      || this.enum.includes(name) && ENUM
      || this.value.includes(name) && VALUE
      // || this.method.includes(name) && METHOD
      || this.func.includes(name) && FUNC
      || this.generic.includes(name) && GENERIC
      || null
  }

  grow (kind) {
    this.local = new Local(this.local, kind)
  }

  drop () {
    this.local = this.local.senior
  }

}

class Checker extends Global {
  constructor (files) {
    super()
    this.file   = null
    this.files  = files
    this.forks  = []
    this.locals = new Array(files.length)
      .fill(null).map(() => new Local(null, MODULE))
  }

  scope (i) {
    this.file   = this.files[i]
    this.local  = this.locals[i]
    this.path   = this.file.path
  }

  fail (msg, start, path) {
    let
      line    = start.line,
      column  = start.column
    path = path || this.path
    throw Error(
      `${msg} at ${path}:${line}:${column} `
    )
  }

  exists (name, start) {
    this.fail(`duplicate ${name} at`, start)
  }

  beNone (id) {
    let name  = id.normalize()
    if (this.get(name))
      this.exists(name, id.loc.start)
    return name
  }

  beExists (name, start, ...kinds) {
    let kind  = this.get(name)

    if (kind && kinds.includes(kind))
      return kind

    this.fail(`missing ${name} of ${ks(kinds)} at`, start)
  }

  orExists (name, start, ...kinds) {
    let kind = this.get(name)
    if (!kind || kinds.includes(kind))
      return kind

    this.fail(`missing ${name} of ${ks(kinds)} at`, start)
  }

  enter () {
    let idx = this.files.length
    while (idx--) {
      this.scope(idx)
      this.file.items.forEach(x => {
        let factor = x.factor
        if (factor) factor.forEach(x => {
          if (x instanceof Array)
            x.forEach(x => this.check(x))
          else if (x)
            this.check(x)
        })
      })
    }
  }

  check (x) {

  }

}

function ks (kinds) {
  return kinds.map(x => x.name).join('|')
}

function firstFork (c, name) {
  let node = null
  c.files.some((file, i) => {
    c.scope(i)
    return file.factor.some(x => {  // use
      return x.items.some(x => {    // import -> module
        return x.items.some(x => { // specifier
          if (!x.fork || x.id.normalize() !== name)
            return false
          node = x
          return true
        })
      })
    })
  })
  return node
}

function firstMoldInMethod (c, name) {
  let node = null
  c.files.some((file, i) => {
    c.scope(i)
    return file.items.some(x => {
      if (
        x.constructor.name !== 'Method'
          || x.id.mold.normalize() !== name
      ) return false
      node = x.id
      return true
    })
  })
  return node
}

function checkConstructor (c, func) {
  let
    name    = func.id.normalize(),
    results = func.mold.results,
    mold    = results.length === 1
      && results[0].mold.normalize()
      || ''

  if (func.mold.yieldable || mold !== 'this' && mold !== name)
    c.fail('illegal constructor', func.id.loc.start)
}

export default function (module) {
  let
    files = module.items,
    c = new Checker(files),
    forks     = [], // 有待实现的 fork
    creators  = [], // 有待检查的构造函数
    molds     = []  // 有待声明的 Method.id.mold

  files.forEach((file, i) => {
    c.scope(i)
    // 收集本地顶级标识符
    file.factor.forEach(x => {  // use
      x.items.forEach(x => {    // import -> module
        let name = x.id.normalize()
        if (name !== '_') {
          if (c.local.get(name))
            c.exists(name, x.id.loc.start)
          c.local.push(name)
        }
        // specifier
        x.items.forEach(x => {
          let name = x.id.normalize()
          if (x.fork) {
            if (c.forks.includes(name))
              c.exists(name, x.id.loc.start)
            forks.push(name)
            c.forks.push(name)
            return
          }
          if (c.local.get(name))
            c.exists(name, x.id.loc.start)
          if (c.local.kind !== SPECIFIER)
            c.grow(SPECIFIER)
          c.local.push(name)
        })
      })
    })

    // 收集全局顶级标识符, 并处理多种形式的 fork:
    //
    // type fork ...
    // enum fork
    // trait fork
    // fn fork
    // fn fork.method
    //

    // files.forEach((file, i) => {
    //   c.scope(i)
    file.items.forEach((x, i) => {
      let name = x.constructor.name

      if (name === 'Func')
      {
        name = x.id.normalize()
        i = c.forks.indexOf(name)
        if (i >= 0)
          forks[i] = ''
        else if (c.orExists(name, x.id.start, TYPE, ENUM))
          checkConstructor(c, x)

        c.func.push(name)
        return
      }

      if (name === 'Method')
      {
        x = x.id
        name = x.mold.normalize()
        i = c.forks.indexOf(name)
        if (i >= 0) forks[i] = ''

        if (
          !c.orExists(name, x.start, TYPE, TRAIT, ENUM, GENERIC)
           && !molds.includes(name)
        )
          molds.push(name)

        name = x.normalize()
        if (c.method.includes(name))
          c.exists(name, x.loc.start)
        c.method.push(name)
        return
      }

      if (name === 'Type')
      {
        name = x.id.normalize()
        i = c.forks.indexOf(name)
        if (i >= 0) forks[i] = ''

        i = molds.indexOf(name)
        if (i >= 0) molds[i] = ''

        let kind = c.get(name)
        if (x.base) switch (x.base.constructor.name) {
          case 'Class':
            if (kind)
              c.exists(name, x.id.loc.start)
            c.class.push(name)
            return
          case 'Slots':
            if (kind)
              c.exists(name, x.id.loc.start)
            c.generic.push(name)
            return
        }
        if (kind === FUNC)
          creators.push(name)
        else if (kind)
          c.exists(name, x.id.loc.start)
        c.type.push(name)
        return
      }

      if (name === 'Trait')
      {
        name = x.id.normalize()
        i = c.forks.indexOf(name)
        if (i >= 0) forks[i] = ''

        i = molds.indexOf(name)
        if (i >= 0) molds[i] = ''

        if (c.get(name))
          c.exists(name, x.id.loc.start)

        c.trait.push(c.beNone(x.id))
        return
      }

      if (name === 'Enum')
      {
        name = x.id.normalize()
        i = c.forks.indexOf(name)
        if (i >= 0) forks[i] = ''

        i = molds.indexOf(name)
        if (i >= 0) molds[i] = ''

        if (c.get(name))
          c.exists(name, x.id.loc.start)

        c.enum.push(c.beNone(x.id))
        return
      }

      if (name === 'Constant')
      {
        x.items.forEach(x => x.result.forEach(x => {
          name = c.beNone(x)
          if (name === '_') return
          c.value.push(name)
          i = c.forks.indexOf(name)
          if (i >= 0) forks[i] = ''
        }))
        return
      }
    })

    forks.forEach(name => {
      if (!name) return
      c.fail(
        `missing implementation fork ${name}`
        , firstFork(c, name).loc.start
      )
    })

  })

  molds.forEach(name => {
    if (!name) return
    c.fail(
      `missing implementation type ${name}`
      , firstMoldInMethod(c, name).loc.start
    )
  })

  if (creators.length)
  {
    let name = creators.shift()
    c.files.some((file, i) => {
      c.scope(i)
      file.items.some(x => {
        if (
          x.constructor.name !== 'Func'
          || x.id.normalize() !== name
        ) return false
        checkConstructor(c, x)
        name = creators.shift()
        return !name
      })
      return !name
    })
  }

  c.enter()
}
